package com.code.point;

import com.alibaba.csp.sentinel.adapter.dubbo.fallback.DubboFallbackRegistry;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.CircuitBreaker;
import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.EventObserverRegistry;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import com.alibaba.csp.sentinel.util.TimeUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.rpc.AsyncRpcResult;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;

import java.util.ArrayList;
import java.util.List;

/**
 * 用户模块启动类
 *
 * @author tangyifei
 */
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class, RabbitAutoConfiguration.class,
        RedisAutoConfiguration.class})
@Slf4j
public class PointApplication {

    private static final String POINT_RES_KEY = "com.code.point.api.PointService";

    public static void main(String[] args) {
        //配置限流规则，这个最好结合sentinel-dashboard进行动态配置
        //initFlowRule();
        //熔断降级规则，这个最好结合sentinel-dashboard进行动态配置
//        initHystrixDegradeRule();
        registerStateChangeObserver();
        //全局统一降级逻辑
        globalDegradeFallbackHandle();
        SpringApplication.run(PointApplication.class, args);
    }

    /**
     * 初始化流量控制规则，这个最好结合sentinel-dashboard进行动态配置
     */
    private static void initFlowRule() {
        //积分服务的流量控制规则
        FlowRule pointFlowRule = new FlowRule();
        //定义资源名称(直接是某一接口中的所有方法)
        pointFlowRule.setResource(POINT_RES_KEY);
        //限流阈值，set limit treads to 10
        pointFlowRule.setCount(10);
        //限流阈值类型，QPS 模式（1）或并发线程数模式（0）,默认是QPS 模式
        pointFlowRule.setGrade(RuleConstant.FLOW_GRADE_THREAD);
        //流控针对的调用来源，默认值default，代表不区分调用来源
        pointFlowRule.setLimitApp("default");

        //流控效果（直接拒绝/WarmUp/匀速+排队等待），不支持按调用关系限流，默认值是直接拒绝，抛出FlowException
        //Warm Up（RuleConstant.CONTROL_BEHAVIOR_WARM_UP）：这种方式，即预热/冷启动方式，
        //当系统长期处于低并发的情况下，流量突然增加到qps的最高峰值，可能会造成系统的瞬间流量过大把系统压垮。
        //所以warmup，相当于处理请求的数量是缓慢增加，经过一段时间以后，到达系统处理请求个数的最大值
        //匀速排队（RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER）: 这种方式会严格控制请求通过的间隔时间，也即是让请求以均匀的速度通过，对应的是漏桶算法它的原理是，
        //以固定的间隔时间让请求通过。当请求过来的时候，如果当前请求距离上个通过的请求通过的时间间隔不小于预设值，
        //则让当前请求通过；否则，计算当前请求的预期通过时间，如果该请求的预期通过时间小于规则预设的 timeout 时间，
        //则该请求会等待直到预设时间到来通过；反之，则马上抛出阻塞异常。可以设置一个最长排队等待时间： flowRule.setMaxQueueingTimeMs(5 * 1000);
        //最长排队等待时间：5s这种方式主要用于处理间隔性突发的流量，例如消息队列。想象一下这样的场景，
        //在某一秒有大量的请求到来，而接下来的几秒则处于空闲状态，我们希望系统能够在接下来的空闲期间逐渐处理这些请求，而不是在第一秒直接拒绝多余的请求。
        //pointFlowRule.setControlBehavior();
        //List<FlowRule> flowRules = Collections.singletonList(pointFlowRule);

        //流控针对的调用来源，若为 default 则不区分调用来源，
        //Sentinel Dubbo Adapter 会自动解析 Dubbo 消费者（调用方）的 application name 作为调用方名称（origin），
        //在进行资源保护的时候都会带上调用方名称
        //若限流规则未配置调用方（default），则该限流规则对所有调用方生效。
        //若限流规则配置了调用方则限流规则将仅对指定调用方生效
        //若调用端引入 Sentinel Dubbo Adapter相关的依赖，Sentinel Dubbo Adapter 实现了一个 Filter 用于自动从 consumer 端向 provider 端透传 application name
        //pointFlowRule.setLimitApp("electronic-commerce-web");
        List<FlowRule> flowRules = new ArrayList<>(1 << 2);
        flowRules.add(pointFlowRule);

        FlowRuleManager.loadRules(flowRules);
    }

    /**
     * 初始化熔断降级规则(对于异常降级仅仅针对业务异常,对 Sentinel 限流降级本身的异常（BlockException）不生效)，这个最好结合sentinel-dashboard进行动态配置
     */
    private static void initHystrixDegradeRule() {
        List<DegradeRule> rules = new ArrayList<>(1 << 3);
        //针对积分服务的熔断降级策略
//        DegradeRule slowRequestDegradeRule = new DegradeRule(POINT_RES_KEY)
//                //熔断策略，支持慢调用比例/异常比例/异常数策略，默认值是慢调用比例
//                .setGrade(CircuitBreakerStrategy.SLOW_REQUEST_RATIO.getType())
//                //慢调用比例模式下为慢调用临界 RT（超出该值计为慢调用）；异常比例/异常数模式下为对应的阈值，慢调用判断的依据
//                //后面的慢调用比例也需要通过这个值进行判定
//                .setCount(10)
//                // Retry timeout (in second)，熔断时长，单位为 s
//                .setTimeWindow(10)
//                //慢调用比例阈值，仅慢调用比例模式有效（1.8.0 引入）
//                //慢调用比例 (SLOW_REQUEST_RATIO)：选择以慢调用比例作为阈值，需要设置允许的慢调用 RT（即最大的响应时间），
//                //请求的响应时间大于该值则统计为慢调用。当单位统计时长（statIntervalMs）内请求数目大于设置的最小请求数目，
//                //并且慢调用的比例大于阈值，则接下来的熔断时长内请求会自动被熔断。
//                //经过熔断时长后熔断器会进入探测恢复状态（HALF-OPEN 状态），
//                //若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断，若大于设置的慢调用 RT 则会再次被熔断。
//                .setSlowRatioThreshold(0.6)
//                //异常比例 (ERROR_RATIO)：当单位统计时长（statIntervalMs）内请求数目大于设置的最小请求数目，
//                //并且异常的比例大于阈值，则接下来的熔断时长内请求会自动被熔断。
//                //经过熔断时长后熔断器会进入探测恢复状态（HALF-OPEN 状态），
//                //若接下来的一个请求成功完成（没有错误）则结束熔断，否则会再次被熔断。
//                //异常比率的阈值范围是 [0.0, 1.0]，代表 0% - 100%。
//                //异常数 (ERROR_COUNT)：当单位统计时长内的异常数目超过阈值之后会自动进行熔断。
//                //经过熔断时长后熔断器会进入探测恢复状态（HALF-OPEN 状态），
//                //若接下来的一个请求成功完成（没有错误）则结束熔断，否则会再次被熔断。
//                //熔断触发的最小请求数，请求数小于该值时即使异常比率超出阈值也不会熔断（1.7.0 引入）
//                .setMinRequestAmount(10)
//                //统计时长（单位为 ms），如 60*1000 代表分钟级（1.8.0 引入）
//                .setStatIntervalMs(2000);
//        rules.add(slowRequestDegradeRule);

        DegradeRule rtDegradeRule = new DegradeRule();
        rtDegradeRule.setResource(POINT_RES_KEY);
        //如果请求的平均响应时间超过指定的阈值200ms，就走熔断
        rtDegradeRule.setCount(10);
        rtDegradeRule.setGrade(RuleConstant.DEGRADE_GRADE_RT);
        //当服务处于不稳定状态时, 就是按照这个时间(10ms)窗口的时间, 响应时间是否超过阈值, 一直这样重复下去，
        //一直到服务资源正常. 这样其实就实现了当后台服务恢复稳定时, Sentinel框架能自动恢复对其的正常请求.
        rtDegradeRule.setTimeWindow(10);
        rules.add(rtDegradeRule);

        DegradeRuleManager.loadRules(rules);
    }

    /**
     * Sentinel 支持注册自定义的事件监听器监听熔断器状态变换事件（state change event）
     */
    private static void registerStateChangeObserver() {
        EventObserverRegistry.getInstance().addStateChangeObserver("logging",
                (prevState, newState, rule, snapshotValue) -> {
                    if (newState == CircuitBreaker.State.OPEN) {
                        log.error(String.format("%s -> OPEN at %d, snapshotValue=%.2f", prevState.name(),
                                TimeUtil.currentTimeMillis(), snapshotValue));
                    } else {
                        log.error(String.format("%s -> %s at %d", prevState.name(), newState.name(),
                                TimeUtil.currentTimeMillis()));
                    }
                });
    }

    /**
     * 全局降级逻辑
     */
    private static void globalDegradeFallbackHandle() {
        DubboFallbackRegistry.setProviderFallback((invoker, invocation, ex) -> {
            log.info("捕获到block异常，服务器处理不过来啦，降级处理", ex);
            return AsyncRpcResult.newDefaultAsyncResult("服务器处理不过来啦", invocation);
        });
    }

}
